using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace SimpleSocket
{
    public sealed class SocketListener
    {
        #region Private Members

        private const int BACKLOG = 500;
        private Socket _socket = null;
        private readonly SocketAsyncEventArgs _ae = null;

        #endregion

        #region Constructors

        public SocketListener(IPEndPoint endPoint)
        {
            this.EndPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint));
            this._ae = new SocketAsyncEventArgs();
            this._ae.Completed += this.AcceptCompleted;
        }

        #endregion

        #region ISocketListener Members

        public event Action<Socket> Accepted;

        public EndPoint EndPoint { get; private set; }

     
        public void Start()
        {
            if (_socket != null) return;
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _socket.Bind(this.EndPoint);
            _socket.Listen(BACKLOG);
            AcceptAsync(_socket);
        }

        /// <summary>
        /// stop
        /// </summary>
        public void Stop()
        {
            if (this._socket != null)
            {
                this._socket.Close();
                this._socket = null;
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// accept socket.
        /// </summary>
        /// <param name="socket"></param>
        private void AcceptAsync(Socket socket)
        {
            if (socket == null) return;

            bool completed = true;
            try
            {
                completed = this._socket.AcceptAsync(this._ae);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            if (!completed) ThreadPool.QueueUserWorkItem(_ => this.AcceptCompleted(this, this._ae));
        }

        /// <summary>
        /// async accept socket completed handle.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AcceptCompleted(object sender, SocketAsyncEventArgs e)
        {
            Socket accepted = null;
            if (e.SocketError == SocketError.Success) accepted = e.AcceptSocket;
            e.AcceptSocket = null;

            if (accepted != null)
                this.Accepted?.Invoke(accepted);

            this.AcceptAsync(this._socket);
        }

        #endregion
    }
}